/****************************************************************************
 * Copyright (C) 2011 Maxim Integrated Products, All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL MAXIM INTEGRATED PRODUCTS BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Maxim Integrated Products
 * shall not be used except as stated in the Maxim Integrated Products
 * Branding Policy.
 *
 * The mere transfer of this software does not imply any licenses
 * of trade secrets, proprietary technology, copyrights, patents,
 * trademarks, maskwork rights, or any other form of intellectual
 * property whatsoever. Maxim Integrated Products retains all ownership rights.
 *
 *     Description: MAXQ Serial I/F (UART) Driver
 *             $Id: maxq_serial1.c 1625 2011-07-01 17:49:20Z jbrodt $
 *
 ********************************************************************************
 */

#include "maxq_config.h"  // always include this file
#include "maxq_serial.h"
#include "maxq_serial_priv.h"
#include "stdlib.h"

#if (NUM_UART > 1)

/***** Definitions *****/
#ifndef SERIAL1_RXB_LEN
#define SERIAL1_RXB_LEN 1
#endif

#ifndef SERIAL1_TXB_LEN
#define SERIAL1_TXB_LEN 1
#endif


/***** Function Prototypes *****/


/***** File Scope Data *****/
static uint8_t rxbuf[SERIAL1_RXB_LEN]; // receive buffer
static uint8_t txbuf[SERIAL1_TXB_LEN]; // transmit buffer


/*****************************************************************************/
int serial1_init(void)
{
  sp[1].parity = SERIAL_PARITY_NONE;
  sp[1].currentBaudrate = BAUD_RATE_115200;

  if(serial_setbaud(sp[1].currentBaudrate, SERIAL_MODE1, sp[1].parity) == SERIAL_UNSUPPORTED_BAUD)
      return SERIAL_GENERAL_ERROR;

  SCON1 &= (~SERIAL_TI);  // clear TI flag
  SCON1 &= (~SERIAL_RI);  // clear RI flag

  SCON1 |= SERIAL_REN;    // received enabled

  SMD1 = SERIAL_ESI;      // serial interrupt enabled;

  sp[1].txEnabled = 1;    // start with transmission enabled
  sp[1].txStatus = 0;     // not currently transmitting
  sp[1].parity_err_flag = 0;  // no error

  fifo_init(&(sp[1].rxf), rxbuf, SERIAL1_RXB_LEN);
  fifo_init(&(sp[1].txf), txbuf, SERIAL1_TXB_LEN);

  return SERIAL_NOERROR;
}

/*****************************************************************************/
static int serial_setbaud(UartBaudRates baudRate, SerialMode mode, SerialParity parity)
{
  int error = SERIAL_NOERROR;

  // ERROR!!!!!  parity not supported in mode0 and mode1 by hardware
  if( ((mode == SERIAL_MODE0) || (mode == SERIAL_MODE1)) && (parity != SERIAL_PARITY_NONE))
      return SERIAL_UNSUPPORTED_OPTION;

  // ERROR!!!!!  parity is MUST in mode2 and mode3 by hardware
  if( ((mode == SERIAL_MODE2) || (mode == SERIAL_MODE3)) && (parity == SERIAL_PARITY_NONE))
      return SERIAL_UNSUPPORTED_OPTION;

/* Baud rates for different modes
  1. for mode1 and mode3 -
            (PR0 * Clk_freq)       5033 * 16MHz (for MAXQ2000 EV Kit)
  B.R.    = ------------------- = ----------------
            (2^23)/(2^(SMOD*2))    8388608/2^(0*2)

  2. for mode0 -
               Clk_freq  *  3^(SM2)
  B.R     =    ---------------------
                        12

  3. for mode2 -
               Clk_freq  *   2^(SMOD)
  B.R     =    -----------------------
                        64
*/

  switch(mode)
  {
      case SERIAL_MODE0:
          switch(baudRate)
          {
              case BAUD_RATE_MODE0L:
                  // clear all mode bits
                  SCON1 &= ~(SERIAL_SM2 | SERIAL_SM1 | SERIAL_SM0); // freq/12

              break;
              case BAUD_RATE_MODE0H:
                  // if modes bit(s) already set do not change
                  if( (SCON1 & (SERIAL_SM2 | SERIAL_SM1 | SERIAL_SM0)) == SERIAL_SM2)
                      break;
                  // clear all mode bits and set requested mode bits
                  SCON1 &= ~(SERIAL_SM2 | SERIAL_SM1 | SERIAL_SM0);
                  SCON1 |= SERIAL_SM2;                              // freq/4
              break;
              default:
                  error = SERIAL_UNSUPPORTED_BAUD;
              break;
           }

      break;

      case SERIAL_MODE1:
      case SERIAL_MODE3:
          if ( (baudRate < BAUD_RATE_1200) || (baudRate > BAUD_RATE_115200) )
            error = SERIAL_UNSUPPORTED_BAUD;
          else
          {
            if (baudRate > (SYS_CLK >> 7)) {  // check request baud rate
              SMD1 |= SERIAL_SMOD;  // set SMOD for no system clock divider
              PR1 = (baudRate * ((double)(8388608.0/4.0) / (double)SYS_CLK));
            }
            else {
              SMD1 &= ~SERIAL_SMOD;  // clear SMOD for system clock divided by 4
              PR1 = (baudRate * ((double)8388608.0 / (double)SYS_CLK));
            }

            // if mode bit(s) already set do not change
            if( (mode == SERIAL_MODE1) && (SCON1 & (SERIAL_SM2 | SERIAL_SM1 | SERIAL_SM0)) == (SERIAL_SM2 | SERIAL_SM1))
              break;
            // if mode bit(s) already set do not change
            if( (mode == SERIAL_MODE3) && (SCON1 & (SERIAL_SM2 | SERIAL_SM1 | SERIAL_SM0)) == (SERIAL_SM1 | SERIAL_SM0))
              break;

            // clear all mode bits and set requested mode bits
            SCON1 &= ~(SERIAL_SM2 | SERIAL_SM1 | SERIAL_SM0);
            if(mode == SERIAL_MODE1)
                SCON1 |= (SERIAL_SM2 | SERIAL_SM1);
            else
                SCON1 |= (SERIAL_SM1 | SERIAL_SM0);

          }
      break;

      case SERIAL_MODE2:
          switch(baudRate)
          {
              case BAUD_RATE_MODE2L:
                  SMD1 |= SERIAL_SMOD;         // freq/64
              break;
              case BAUD_RATE_MODE2H:
                  SMD1 &= SERIAL_SMOD;         // freq/32
              break;
              default:
                  error = SERIAL_UNSUPPORTED_BAUD;
              break;
          }

           if(error != SERIAL_UNSUPPORTED_BAUD)
           {
               // if modes bit(s) already set do not change
               if( (SCON1 & (SERIAL_SM2 | SERIAL_SM1 | SERIAL_SM0)) == SERIAL_SM0)
                   break;

               // clear all mode bits and set requested mode bits
               SCON1 &= ~(SERIAL_SM2 | SERIAL_SM1 | SERIAL_SM0);
               SCON1 |= SERIAL_SM0;
           }

      break;
   }

  return error;
}

/*****************************************************************************/
int serial1_setparameters(SerialMode mode, UartBaudRates baud, SerialParity parity)
{
    int error = SERIAL_NOERROR;

    if((error = serial_setbaud(baud, mode, parity)) == SERIAL_NOERROR) {
         sp[1].parity = parity;
         sp[1].currentBaudrate = baud;
    }

    return error;
}

/*****************************************************************************/
void serial1_flush(void)
{
  uint8_t istate;

  // atomic FIFO access
  istate = IC;  // save interrupt enabled state
  IC &= ~0x01;  // disable interrupts globally

  fifo_init(&(sp[1].rxf), rxbuf, SERIAL1_RXB_LEN);
  fifo_init(&(sp[1].txf), txbuf, SERIAL1_TXB_LEN);
  sp[1].txEnabled = 1;    // start with transmission enabled
  sp[1].txStatus = 0;     // not currently transmitting
  sp[1].parity_err_flag = 0;  // no error

  IC |= (istate & 0x01);  // restore interrupt enabled state
}

/*****************************************************************************/
void serial1_getparameters(SerialMode* mode, UartBaudRates* baud, SerialParity* parity)
{
    *parity = sp[1].parity;
    *baud = sp[1].currentBaudrate;
    *mode = (SerialMode)((SCON1 & 0x0C) >> 6);
}

/*****************************************************************************/
int serial1_write(uint8_t* outstr, unsigned int Len, unsigned int* dataLen)
{
  unsigned int count;
  int err = SERIAL_NOERROR;

  // user wants BUFFER_EMPTY status
  if( (Len == 0) && fifo_empty(&(sp[1].txf)))
    return SERIAL_BUFFER_EMPTY;

  // copy to tx buffer the 'Len' bytes. if txbuffer is full,
  // return 'number_of_bytes' we wrote
  for(count = 0; count < Len; count++) {
    err = serial1_putchar(outstr[count]);
    if(err != SERIAL_NOERROR)
      break;
  }

  *dataLen = count;
  return err;
}

/*****************************************************************************/
int serial1_putchar(uint8_t outchar)
{
  // write character to transmit buffer
  if (!fifo_put(&(sp[1].txf), outchar))
     return SERIAL_BUFFER_FULL;

  if (!sp[1].txStatus) {  // if not currently transmitting
      sp[1].txStatus = 1; // change start to transmitting
      SCON1 |= SERIAL_TI; // trigger interrupt to start transmission
  }

  return SERIAL_NOERROR;
}

/*****************************************************************************/
int serial1_read(uint8_t* instr, unsigned int maxlen, unsigned int* readLen)
{
  unsigned int count = 0;
  int err;

  // check if caller has valid storage buffer
  if (instr == NULL) {
    *readLen = 0;
    return SERIAL_GENERAL_ERROR;
  }

  for (count = 0; count < maxlen; count++) {
    if( (err = serial1_getchar(&(instr[count]))) != SERIAL_NOERROR)
      break;
  }

  *readLen = count;

  return err;
}

/*****************************************************************************/
int serial1_getchar(uint8_t* inchar)
{
  if (!fifo_get(&(sp[1].rxf), inchar))
    return SERIAL_BUFFER_EMPTY;

  if(sp[1].parity_err_flag) {
    sp[1].parity_err_flag = 0;
    return SERIAL_PARITY_ERROR;
  }

  return SERIAL_NOERROR;
}

/*****************************************************************************/
void serial1_tx_stop(void)
{
    sp[1].txEnabled = 0;
}

/*****************************************************************************/
void serial1_tx_resume(void)
{
    sp[1].txEnabled = 1;
    SCON1 |= SERIAL_TI;    // trigger transmit interrupt
}

#endif  /* (NUM_UART > 1) */
